package log

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"
	"time"

	"backend/config"

	"go.uber.org/zap/zapcore"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

// newLogger 创建并初始化zap日志库
func newLogger(cfg *config.Log) *zap.Logger {
	path, err := createFileInProjectDir(cfg.FilePath)
	if err != nil {
		panic(fmt.Errorf("创建日志文件失败: %w", err))
	}

	// 设置日志级别,从配置文件中读取
	var level zapcore.Level

	logLevelMapper := map[int]zapcore.Level{
		-1: zap.DebugLevel,
		0:  zap.InfoLevel,
		1:  zap.WarnLevel,
		2:  zap.ErrorLevel,
		3:  zap.DPanicLevel,
		4:  zap.PanicLevel,
		5:  zap.FatalLevel,
	}

	if l, ok := logLevelMapper[cfg.Level]; ok {
		level = l
	} else {
		level = zap.InfoLevel
	}

	// 打开日志文件
	logFile, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
	if err != nil {
		panic(fmt.Errorf("打开日志文件失败: %w", err))
	}
	// defer logFile.Close()

	// 设置日志输出路径和错误输出路径
	encoderConfig := zap.NewProductionEncoderConfig()
	cores := zapcore.NewCore(
		zapcore.NewJSONEncoder(encoderConfig),
		zapcore.NewMultiWriteSyncer(
			zapcore.AddSync(os.Stdout), // 输出到标准输出
			zapcore.AddSync(logFile),   // 输出到文件
		),
		level,
	)

	// 构建zap日志对象
	logger := zap.New(cores, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))

	return logger
}

// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery

		// 由于c.Request.Body只允许读一次,读完之后内容会被删除,因此在这里我们读取Body中的内容,然后存储在变量中
		bodyBytes, _ := io.ReadAll(c.Request.Body)
		c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

		c.Next()

		cost := time.Since(start)
		c.Set("cost", cost) // 将耗时存储在context中, 以便后续使用
		logger.Info(path,
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.Any("head", c.Request.Header),
			zap.String("path", path),
			zap.String("query", query),
			zap.String("body", string(bodyBytes)),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost),
			zap.String("request_id", c.Request.Header.Get("X-Request-Id")),
		)
	}
}

// GinRecovery recover掉项目可能出现的panic
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					logger.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					_ = c.Error(err.(error)) // Ignore the error return value
					c.Abort()
					return
				}

				if stack {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

// createFileInProjectDir 在项目目录下创建文件
func createFileInProjectDir(fileName string) (string, error) {
	// 1. 获取当前工作目录
	projectDir, err := os.Getwd()
	if err != nil {
		return "", fmt.Errorf("无法获取当前工作目录: %w", err)
	}

	// 2. 构建完整文件路径
	filePath := filepath.Join(projectDir, fileName)

	// 3. 检查路径是否合法（防止路径穿越）
	if !isPathInProjectDir(filePath, projectDir) {
		return "", fmt.Errorf("文件路径非法: %s 不在项目目录下", filePath)
	}

	// 4. 确保目标目录存在
	dir := filepath.Dir(filePath)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return "", fmt.Errorf("创建目录失败: %w", err)
	}

	// 5. 创建文件, 如果文件已存在则跳过
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		_, err := os.Create(filePath)
		if err != nil {
			return "", fmt.Errorf("创建文件失败: %w", err)
		}
		// defer file.Close()
	}

	return filePath, nil
}

// isPathInProjectDir 检查文件路径是否在项目目录下
func isPathInProjectDir(filePath, projectDir string) bool {
	// 将路径标准化（处理路径分隔符差异）
	absFilePath, err := filepath.Abs(filePath)
	if err != nil {
		return false
	}
	absProjectDir, err := filepath.Abs(projectDir)
	if err != nil {
		return false
	}

	// 检查文件路径是否以项目目录为前缀
	return filepath.HasPrefix(absFilePath, absProjectDir)
}
